package core;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import exception.ConstraintException;
import exception.DataException;
import exception.NoSuchColumnFoundException;

public class Record {
	private Table table;
	private HashMap<Column, String> data;
	
	public Record() {
		table = null;
		data = new HashMap<Column, String>();
	}
	
	public void insert(LinkedHashMap<String, Column> columns, HashMap<String, String> values) throws DataException, NoSuchColumnFoundException, ConstraintException {
		HashMap<Column, String> tempData = new HashMap<Column, String>();
		boolean dataValid = false;
		
		for(Entry<String, String> entry : values.entrySet()) {
			if(!columns.containsKey(entry.getKey())){
				throw new NoSuchColumnFoundException("Column '"+ entry.getKey() +"' does not exists");
			}
			
			Column column = columns.get(entry.getKey());
			table = column.getTable();
			
			String value = entry.getValue();
			if(value.isEmpty() && !column.getDataType().equals("VARCHAR")) {
				value = "0";
			}
			
			if(column.getCharacterLength() > 0 && value.length() > column.getCharacterLength()) {
				throw new DataException("Data too long, got "+ value.length() +" characters, max "+ column.getCharacterLength());
			}
			
			dataValid = isValid(column, value);
			if(!dataValid) {
				break;
			} else {
				tempData.put(column, value);
			}
		}
		
		hasValidPrimaryKey(tempData);
		hasValidForeignKey(tempData);
		
		if(dataValid) {
			data.putAll(tempData);
		} else {
			throw new DataException("Data was not valid");
		}
	}
	
	private boolean isValid(Column column, String value) throws DataException {
		
		if(column.getDataType().equals("VARCHAR")) {
			// Varchar is alles, dus daarom hebben we geen check.
			return true;
		} else if(column.getDataType().equals("INTEGER")) {
			return Pattern.matches("-?\\d+", value);
		} else if(column.getDataType().equals("REAL")) {
			return Pattern.matches("-?\\d+(\\.\\d+)?", value);
		} else if(column.getDataType().equals("BOOLEAN")) {
			return Pattern.matches("[Tt]rue|[Ff]alse|1|0", value);
		} else {
			throw new DataException("Data type '"+ value.getClass().getName() +"' is not supported");
		}
	}
	
	private void hasValidPrimaryKey(HashMap<Column, String> tempData) throws DataException, ConstraintException {
		Constraint primaryKey = table.getPrimaryKeyConstraint();
		
		// Als er geen primary key is dan moeten we true returnen omdat dubbele data
		// nu gewoon geldig is. Als we hier false returnen gaat de methode altijd onderuit
		// en kunnen we geen dubbele data invoeren.		
		if(primaryKey != null) {
			for(Record record : table.getRecords()) {
				boolean isValid = false;
				
				for(Entry<Column, String> entry : record.getData().entrySet()) {
					if(primaryKey.hasColumn(entry.getKey())) {
						if(!entry.getValue().equals(tempData.get(entry.getKey()))) {
							isValid = true;
							break;
						}
					}
				}
				
				if(!isValid) {
					throw new ConstraintException("Duplicate entry");
				}
			}
		}
	}
	
	private void hasValidForeignKey(HashMap<Column, String> tempData) throws DataException, NoSuchColumnFoundException, ConstraintException {
		HashMap<String, Constraint> foreignKeyConstraints = table.getForeignKeyConstraints();
		
		if(foreignKeyConstraints.size() > 0) {
			for(Entry<String, Constraint> entry : table.getForeignKeyConstraints().entrySet()) {
				for(ConstraintReference constraintReference : entry.getValue().getContraintReference()) {
					boolean foundData = false;
					
					HashMap<String, String> data = constraintReference.getRefColumn().getTable().select(constraintReference.getRefColumn().getColumnName(), new String[] { });
					for(Entry<String, String> value : data.entrySet()) {
						if(value.getValue().equals(tempData.get(constraintReference.getColumn()))) {
							foundData = true;
							break;
						}
					}
					
					if(!foundData) {
						throw new ConstraintException("Cannot add or update a child row: a foreign key constraint fails");
					}
				}
			}
		}
	}
	
	public HashMap<Column, String> getData() {
		return data;
	}
	
	@Override
	public String toString() {
		String output = "";
		for(Entry<Column, String> entry : data.entrySet()) {
			output += entry.getValue() + " : ";
		}
		
		return output.substring(0, output.length() - 3);
	}
}
